iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0
Python

眾裏尋它:Python表格利器Great Tables系列 第 28

[Day28] - 如何與FastHTML整合 - 靜態表格

  • 分享至 

  • xImage
  •  

今天我們將了解如何將靜態的gt表格呈現於FastHTML app中,並編寫裝飾器來協助表格呈現。

明天則會編寫一個可以動態改變gt表格背景顏色的FastHTML app,並使用pytest與Playwright進行測試。

今明兩天的內容參考自官方Solar Zenith Angles範例

靜態表格

步驟1:建立fast_app instance

建立一個名為appfast_app instance及一個rt object。

# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
from functools import cache

import polars as pl
from fasthtml.common import NotStr, Title, Titled, fast_app
from great_tables import GT, html
from great_tables.data import sza

app, rt = fast_app()

其中rt是FastHTML提供的快速路由功能,其可以偵測所裝飾的函數名,是否為getpost等http操作,進而添加路由功能。舉例來說,將@app.get("/")及將@rt("/")裝飾在get()函數上,對FastHTML來說是等義的操作。

步驟2:建立回傳Polars DataFrame的get_sza()函數

建立get_sza()函數,其會回傳一個Polars DataFrame。由於在此app中,DataFrame並不會變動,所以這邊可以使用@cache裝飾在get_sza()之上。

# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
@cache
def get_sza():
    return pl.from_pandas(sza)

步驟3:建立回傳表格的get()函數

使用@rt("/")來定義get(),作為使用者以HTTP GET訪問「"/"」時所使用的函數。

# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
@rt("/")
def get():
    sza_pivot = (
        get_sza()
        .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
        .select(pl.col("*").exclude("latitude"))
        .drop_nulls()
        .pivot(values="sza", index="month", on="tst", sort_columns=True)
    )

    sza_gt = (
        GT(sza_pivot, rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=["rebeccapurple", "white", "orange"],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

    return (
        Title("FastHTML-GT Website"),
        H1("Great Tables shown in FastHTML", style="text-align:center"),
        NotStr(sza_gt.as_raw_html())
    )

分段說明如下:

  • sza_pivot為Polars DataFrame的整理過程;sza_gt則為表格的製作過程。
  • 直接回傳各種HTML的tag或FastHTML component:
    • Title對應HTML中的title tag。Title("FastHTML-GT Website")相當於將title tag設為「"FastHTML-GT Website"」。
    • H1("Great Tables shown in FastHTML", style="text-align:center")相當於將h1 tag設為「"Great Tables shown in FastHTML"」以及增加「"text-align:center"」至CSS中。
    • NotStr為內建的FastHTML component,可以將其接收字串視為HTML格式。因此我們可以使用GT.as_raw_html()來生成HTML,並置於其中。

這樣的寫法對於年輕的朋友可能會有點不習慣,像是在寫FastAPI,卻要在不使用templates.TemplateResponse的情況下回傳HTML,而不是JSON。

但是依稀記得在千禧年前後,那段還是用FrontPage或是Dreamweaver架站的時代,接收HTML為回傳值不是很正常的嗎?:)

步驟4:啟動Uvicorn server

於命令列中執行下列指令:

uvicorn main:app --reload

接著打開瀏覽器前往預設網址,如http://127.0.0.1:8000/,就可以見到下面這個漂亮的表格:

table

編寫裝飾器

我們希望能夠編寫一個裝飾器來簡化NotStr(sza_gt.as_raw_html()),目標是使其能夠裝飾於一個回傳GT instance的函數上。實作程式碼如下:

# https://github.com/jrycw/ft-gt-demo/blob/master/ft_gt/__init__.py
from functools import partial, wraps

from fasthtml.common import Div, NotStr


def gt2fasthtml(func=None, **div_kwargs):
    """
    https://pybit.es/articles/decorator-optional-argument/
    """
    if func is None:
        return partial(gt2fasthtml, **div_kwargs)

    @wraps(func)
    def wrapper(*args, **kwargs):
        gtbl = func(*args, **kwargs)
        gtbl_html = gtbl.as_raw_html()
        return Div(NotStr(gtbl_html), **div_kwargs)

    return wrapper

一開頭我們以func是不是None來判斷該裝飾器是以@gt2fasthtml或是@gt2fasthtml()的方式來呼叫。這兩種呼叫方式將是等義的,是種增加使用者體驗的設計。

接著在wrapper()函數內:

  • 呼叫func(*args, **kwargs)取得所裝飾函數的結果,即一個GT instance。
  • 呼叫GT.as_raw_html()取得表格的HTML格式。
  • 此表格HTML將使用NotStr包裹後,再使用Div包裹。這邊請留意,我們將允許使用者自gt2fasthtml()來傳入Div的屬性。例如@gt2fasthtml(id="gt"),就代表將id="gt"傳入給Div

如果對裝飾器這個概念仍然不是那麼清楚的朋友,可以參考我於2023年鐵人賽的系列文 ── Python十翼:與未來的自己對話,其中有詳細的說明。

測試

我們可以編寫測試,來看看裝飾器是否正常運作,有興趣的朋友可以參考下列程式碼,了解其五種使用情境。

# https://github.com/jrycw/ft-gt-demo/blob/master/tests/test_basic.py
import fastcore

from ft_gt import gt2fasthtml


def test_dec_without_parentheses(gtbl):
    @gt2fasthtml  # gt2fasthtml, without parentheses
    def get_gtbl():
        return gtbl

    div_comp = get_gtbl()

    assert isinstance(div_comp, fastcore.xml.FT)


def test_dec_with_parentheses(gtbl):
    @gt2fasthtml()  # gt2fasthtml(), with parentheses
    def get_gtbl():
        return gtbl

    div_comp = get_gtbl()

    assert isinstance(div_comp, fastcore.xml.FT)


def test_dec_with_div_kwargs(gtbl):
    @gt2fasthtml(id="gt")
    def get_gtbl():
        return gtbl

    div_comp = get_gtbl()

    assert isinstance(div_comp, fastcore.xml.FT)
    assert div_comp.attrs["id"] == "gt"


def test_func_with_parentheses(gtbl):
    def get_gtbl():
        return gtbl

    get_gtbl = gt2fasthtml(get_gtbl)
    div_comp = get_gtbl()

    assert isinstance(div_comp, fastcore.xml.FT)


def test_func_with_div_kwargs(gtbl):
    def get_gtbl():
        return gtbl

    get_gtbl = gt2fasthtml(get_gtbl, id="gt")
    div_comp = get_gtbl()

    assert isinstance(div_comp, fastcore.xml.FT)
    assert div_comp.attrs["id"] == "gt"

Code

靜態表格可參考gt-fasthtml repo,裝飾器及其測試則可參考ft-gt-demo repo。


上一篇
[Day27] - 如何與Panel整合 - 靜態與動態表格
下一篇
[Day29] - 如何與FastHTML整合 - 動態表格
系列文
眾裏尋它:Python表格利器Great Tables30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言